/**
 * declare var self: ServiceWorkerGlobalScope
 */
console.log('---1', self);
console.log(`serviceWorker可以干这些事情：
    跨域资源请求
    数据加载的优化（预加载、数据同步、后台更新-旧的资源仍可用，但有新的较大资源）即使页面已经关闭了，serviceWorker仍然可以后台运行，进行数据更新维护
    客户端编译和依赖管理，如less，cjs，typescript等，主要用于开发的目的
    作为服务端，响应某些请求。
    提供hook？
`);
/**
 * --------------------------------------------------------------------------------------------------------------
 * 存储管理
 */
if (self.navigator.storage.persist) {
  console.log('--- start to apply for persist ---');
  self.navigator.storage.persist().then((flag) => {
    console.log('--- apply for persist result:', flag);
    if (flag) {
      // 成功将 将box-mode转化成“persistent” 模式
      // 默认 best-effort，浏览器根据网站最近访问情况、访问频繁度等决定缓存额度，并且在空间不够时，会静默删除整个缓存空间。
      // 需要删除空间时，要么不删，要么删除整个（localStorage、indexDB、CacheStorage 都在一个box中）
      // persistent 空间不够时优先删除best-effort网站的空间，最后才开始删除persistent的网站，并且删除时会给出通知提示，让用户选择删哪些。
      // persistent 模式下，浏览器不会主动删，要么网站有明确的删除代码，要么用户授权删除。
    }
  });
}

self.navigator.storage.persisted().then((flag) => {
  console.log('-- is user authorized for persist?', flag ? 'yes' : 'no');
  if (flag) {
    // 已经获取了persistent权限，无需再调用 persist 了！
  }
});

self.navigator.storage.estimate().then((estimate) => {
  // estimate 估计
  console.log('缓存空间的大概额度（字节）：', estimate.quota);
  console.log('缓存空间大概已使用（字节）：', estimate.usage);
});

self.navigator.storage.getDirectory().then((rootHandle) => {
  console.log('操作文件吗？', rootHandle);
  // 这里可以操作目录，操作文件，包括：创建文件、读文件、写文件
  // 这是浏览器端 唯一可以操作文件系统的地方了：https环境下，操作自己的缓存目录
});

/**
 * ----------------------------------------------------------------------------------------------------------------
 * caches在页面脚本或在serviceWorker中操作 都是一样的，都是在Domain的CacheStorage下
 * 经过测试，放入的请求会有：method限制（不支持Post） schema限制（不支持chrome-extension协议）
 *
 * 总是报错：Failed to execute 'respondWith' on 'FetchEvent': The event handler is already finished.
 * 使用 e.waitUntil 也没有用！
 * 只有 立即/同步 执行 e.respondWith(...) ，所有逻辑都在这里面完成，才能避免这个报错。
 *
 * 报错：Failed to execute 'put' on 'Cache': Response body is already used
 * 这是因为在调用e.respondWith()之前，必须先cache.put()。不用等待put完成，只要先put 就行。
 * 此外respond出去之前，也不能clone这个res。总之对res的操作，先做完，最后才能respond出去。
 *
 * 报错：a Response whose "body" is locked cannot be used to respond to a request
 * 发出去的res没有锁定，而放入cache的会被锁定，所以放入cache时要放入的是 res.clone()
 *
 * 或者直接 响应一个 respond.clone() ，这样放入cache的过程就可以延后执行了？ 是的，而且放入时放入res本身即可。
 *
 * 【重要】不应该优先取缓存数据 作为响应，否则服务端资源修改了 页面将得不到更新。应该是这样的：
 * 每次都真正发起请求，成功后放入cache 并响应。如果失败（网络、服务端繁忙）再取cache作为响应。
 * 【官方】只缓存不会改变的资源，比如字体。
 *
 * 官方示例中req也要clone，同时被Cache.put和fetch消费会有冲突，但是这里没有冲突。
 *
 */

self.caches.open('my1').then((cache) => {
  console.log('成功开启cache');
  /**
   * new Request(urlToPrefetch, {mode: 'no-cors'})
   * it's very important to use {mode: 'no-cors'} if there is any chance that the resources are served off of a server that doesn't support CORS.
   */
  cache.put('xxx', new Response('xxxx')); // 这是相对于serviceWorker路径的，不是页面路径！
  // cache.addAll(['/api1', '/api2']);
});

self.addEventListener('fetch', (e) => {
  /** @type {Request} */
  const request = e.request;
  console.log('=== url:', request.method + ' ' + request.url);
  if (['POST', 'PUT'].includes(request.method) || request.url.startsWith('chrome-extension')) {
    console.log('%c**不能放到Cache中**', 'color:lightgreen');
  } else {
    e.respondWith(
      (async () => {
        return fetch(request)
          .then((res) => {
            if (!res.ok) {
              // 响应的状态码是否表示成功，即为 200-299
              console.log('%c**请求非ok**', 'color:lightgreen');
            } else {
              caches.open('my1').then((cache) => {
                cache
                  .put(request, res)
                  .then(() => {
                    console.log('%c缓存成功', 'color:green');
                  })
                  .catch((e) => {
                    console.dir(e);
                  });
              });
            }
            return res.clone();
          })
          .catch(async (err) => {
            const response = await caches.match(request);
            if (response) {
              console.log('使用缓存===');
              return response;
            } else {
              throw err;
            }
          });
      })()
    );
  }
});

/**
 * ----------------------------------------------------------------------------------------------------------------
 * worker 的 ExtendableEvent，（SPEC 规范定义专用于ServiceWorker）
 * 唯一特殊的方法是waitUntil，传入promise。只能在 event handler内部调用，外部调用将抛出异常。可以多次调用。
 *
 * install事件中调用：worker register将一直处于installing状态，只到传入的所有promise都settle了
 * （都resolve了，install才算完成，有reject了就算安装失败，当前worker将被舍弃）
 * 常用于确保serviceWorker所依赖的资源加载成功了
 *
 * activate（注意拼写，不是active） 事件中调用：promise结束前，serviceWorker的后续事件（fetch事件，push事件）将先缓存在buffer中。
 * 通常在里面执行缓存schema升级，删除旧数据库等，确保后续的操作是基于完成升级后的。
 * ———— 这些升级也只能在这个事件中进行 才安全！
 *
 * 此外还有：fetch事件
 */
self.addEventListener('install', (event) => {
  console.log('监听到【 install 】事件');
  // The promise that skipWaiting() returns can be safely ignored.
  self.skipWaiting(); // install后立马激活

  // event.waitUntil();
});

self.addEventListener('activate', (event) => {
  console.log('监听到【 activate 】事件');
  event.waitUntil(
    /** 接管 scope下所有clients。接管转换会在 client侧（即ServiceWorkerContainer）触发 controllerchange 事件
     * scope就是 worker文件uri 相关的标识
     * 不调用clients.claim()的话，页面必须重新加载，才能使用新的worker。
     */
    self.clients.claim()
  );
});
